﻿/*
 * Created by SharpDevelop.
 * User: v-edwild
 * Date: 04/07/2007
 * Time: 17:28
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */

using System;
using System.Collections.ObjectModel;
using System.Globalization;
using WatiN.Core;
using Wax.Properties;

namespace Wax
{
	/// <summary>
	/// Responsible for creating instances or derived types of <see cref="Command" />.
	/// </summary>
	public sealed class CommandFactory
	{
		private CommandFactory() {}
		
		#region Public static methods
		
		/// <summary>
		/// <para>Parses the input args to create a command object. For a complete list of commands please see the help documentation.</para>
		/// <para>
		/// <b>Args layout</b>
		/// <list type="table">
		/// 	<item>
		/// 		<term>1. Id</term>
		/// 		<description>Id of the command</description>
		/// 	</item>
		/// 	<item>
		/// 		<term>2. Command</term>
		/// 		<description><see cref="CommandType">MethodType</see> of command to execute</description>
		/// 	</item>
		/// 	<item>
		/// 		<term>3. Command Args (optional, specify null)</term>
		/// 		<description>Comma separated list of <see cref="Command.Arguments">arguments</see> used when executing a <see cref="Command" /></description>
		/// 	</item>
		/// 	<item>
		/// 		<term>4. FindMethod (optional, specify null)</term>
		/// 		<description><see cref="FindMethodTypes">MethodType</see> of find method used by <see cref="FindMethod"/> to locate an <see cref="WatiN.Core.Element">Element</see></description>
		/// 	</item>
		/// 	<item>
		/// 		<term>5. FindMethod arguments (optional, specify null)</term>
		/// 		<description>Comma separated list of <see cref="FindMethod.Arguments">arguments</see> used by <see cref="FindMethod"/> to locate an <see cref="WatiN.Core.Element">Element</see></description>
		/// 	</item>
		/// </list>
		/// </para>
		/// <para>The list of supported commands are as follows:
		/// <list type="table">
        /// 	<item>
        /// 		<term><see cref="CommandType.AttachToIE">AttachToIE</see>. Used to attach to a running instance of Internet Explorer.</term>
        ///			<description>Required: Find method and find method arg. Example: AttachToIETest, AttachToIE, string.Empty,ById,"Test page 2"</description>
        /// 	</item>
        /// 	<item>
		/// 		<term><see cref="CommandType.Back">Back</see></term>
		/// 		<description>No command args, find method or find method args should be specified</description>
		/// 	</item>
        /// 	<item>
        /// 		<term><see cref="CommandType.Close">Close</see></term>
        /// 		<description>
        ///             <para>Closes the current instance of Internet Explorer</para>
        ///             <para>No command args, find method or find method args should be specified</para>
        ///         </description>
        /// 	</item>
        /// 	<item>
        /// 		<term><see cref="CommandType.Flash">Flash</see></term>
        /// 		<description>
        ///             <para>Causes the current element to flash</para>
        ///             <para>No command args, find method or find method args should be specified</para>
        ///         </description>
        /// 	</item>        
        /// 	<item>
		/// 		<term><see cref="CommandType.Click">Click</see></term>
		/// 		<description>Required: Find method and find method args. Example: btnSubmit, Click,,ById,btnSubmitId</description>
		/// 	</item>
		/// 	<item>
		/// 		<term><see cref="CommandType.DoubleClick">DoubleClick</see></term>
		/// 		<description>Required: Find method and find method arg. Example: btnSubmit, Click,,ById,btnSubmitId</description>
		/// 	</item>
		/// 	<item>
		/// 		<term><see cref="CommandType.Forward">Forward</see></term>
		/// 		<description>No command args, find method or find method args should be specified</description>
		/// 	</item>
		/// 	<item>
		/// 		<term><see cref="CommandType.GoTo">Goto</see></term>
		/// 		<description>Required command args. Example: GotoCommand1, homepage.html</description>
		/// 	</item>
		/// 	<item>
		/// 		<term><see cref="CommandType.SetValue">SetValue</see>. Used to set the value of an element</term>
		///			<description>Required: Find method and find method arg. Example: SetValueCmd4, SetValue,"Hello World,Text",ById,txtFirstName</description>
		/// 	</item>
        ///		<item>	
		/// 		<term><see cref="CommandType.Validate">Validate</see>. Used to validate the value of an element</term>
		///			<description>
		/// 			<para>Required: Find method and find method arg. Example: ValidateCmd6, Validate,"MyExpectedValue,Text",ById,txtFirstName</para>
		/// 			<para>For supported properties see:<see cref="PropertyType" /></para>
		/// 		</description>
		/// 	</item>
        ///		<item>	
        /// 		<term><see cref="CommandType.ValidateItems">ValidateItems</see>. Used to validate the values of an element</term>
        ///			<description>
        /// 			<para>Required: Find method and find method arg. Example: ValidateCmd7, ValidateItems, "Visa,MasterCard,SelectedItem", ById, cardList</para>
        /// 			<para>For supported properties see:<see cref="PropertyType" /></para>
        /// 		</description>
        /// 	</item>
        ///     <item>	
        /// 		<term><see cref="CommandType.Settings">Settings</see>. Used to override the default <see cref="IE.Settings"/>.</term>
        ///			<description>
        /// 			<para>Example: SettingsCmd, Settings, "HighlightColor,Red"</para>
        /// 			<para>For values properties see:<see cref="SettingsCommand.SettingsType" /></para>
        /// 		</description>
        /// 	</item>
		/// </list>
		/// </para>
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		public static Command Parse(string[] args)
		{
			if (args == null)
			{
				throw new ArgumentNullException("args");
			}
			
			if (args.Length != 5)
			{
                throw new ArgumentException(Resource.CommandFactoryParseArgumentLengthException, "args");
			}
			
			string commandName = args[0];
			string commandType = args[1];
			string[] commandArgs = args[2] != null ? ParseArguments(args[2]) : null;
			string findMethodType = args[3];
			string[] findMethodArgs = args[4] != null ? args[4].Split(',') : null;
			
			// Validate reqired combination of arguments
			if (string.IsNullOrEmpty(commandType))
			{
				throw new InvalidCommandException("commandType", "You must specify a command type");
			}
			
			CommandType type = ParseCommandType(commandType);
			Command parsedCommand = Create(type);
			parsedCommand.Id = commandName;
			parsedCommand.CommandType = type;
			if (commandArgs != null)
			{
			    foreach (string commandArg in commandArgs)
			    {
			        parsedCommand.Arguments.Add(commandArg);    
			    }
			}
			
			if (!ValidateRequiredCommandArguments(parsedCommand))
			{
				throw new ArgumentOutOfRangeException("args", string.Format(CultureInfo.InvariantCulture, "The required arguments for the command: {0} were not given", parsedCommand.CommandType));
			}
			
			if (!string.IsNullOrEmpty(findMethodType))
			{
				parsedCommand.FindMethod = FindMethod.Parse(findMethodType, findMethodArgs);	
			}
			          
            parsedCommand.Initialize();

			return parsedCommand;
		}
		
		/// <summary>
		/// Parses a partial string representation of a <see cref="CommandType" />. For example "Click" will
		/// be parsed and returned as CommandType.Click.
		/// </summary>
		/// <param name="command">Partial string representation of a <see cref="CommandType" /></param>
		/// <returns>Parsed value as a <see cref="CommandType" /></returns>
		public static CommandType ParseCommandType(string command)
		{
			
            if (string.IsNullOrEmpty(command))
            {
                throw new ArgumentNullException("command");
            }
            
            CommandType returnValue;

		    if (command.Equals("click", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = CommandType.Click;
		    }
		    else if (command.Equals("doubleclick", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = CommandType.DoubleClick;
		    }
            else if (command.Equals("goto", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = CommandType.GoTo;
		    }
            else if (command.Equals("setvalue", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = CommandType.SetValue;
		    }
            else if (command.Equals("setvalues", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = CommandType.SetValues;
		    }
            else if (command.Equals("back", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.Back;
            }
            else if (command.Equals("bringtofront", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.BringToFront;
            }
            else if (command.Equals("forward", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.Forward;
            }
            else if (command.Equals("validate", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.Validate;
            }
            else if (command.Equals("attachtoie", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.AttachToIE;
            }
            else if (command.Equals("validateitems", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.ValidateItems;
            }
            else if (command.Equals("close", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.Close;
            }
            else if (command.Equals("flash", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.Flash;
            }
            else if (command.Equals("refresh", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.Refresh;
            }
            else if (command.Equals("presstab", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.PressTab;
            }
            else if (command.Equals("focus", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.Focus;
            }
            else if (command.Equals("typetext", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.TypeText;
            }
            else if (command.Equals("appendtext", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.AppendText;
            }
            else if (command.Equals("cleartext", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.ClearText;
            }
            else if (command.Equals("selecttext", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.SelectText;
            }
            else if (command.Equals("clearcache", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.ClearCache;
            }
            else if (command.Equals("clearcookies", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.ClearCookies;
            }
            else if (command.Equals("screenshot", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.Screenshot;
            }
            else if (command.Equals("setcookie", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.SetCookie;
            }
            else if (command.Equals("getcookie", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.GetCookie;
            }
            else if (command.Equals("validatelastfound", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.ValidateLastFound;
            }
            else if (command.Equals("attachdialoghandler", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = CommandType.AttachDialogHandler;
		    }
            else if (command.Equals("GetJavaDialogMessage", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.GetJavaDialogMessage;
            }
            else if (command.Equals("ClickNoWait", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.ClickNoWait;
            }
            else if (command.Equals("Settings", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = CommandType.Settings;
            }
            else
            {
                throw new ArgumentOutOfRangeException("command",
                                                      string.Format(CultureInfo.InvariantCulture,
                                                                    "Can not parse unrecognised command type: {0}.", command));
            }

		    return returnValue;
		}


        /// <summary>
        /// Parses the arguments.
        /// </summary>
        /// <param name="arguments">The arguments.</param>
        /// <returns></returns>
        public static string[] ParseArguments(string arguments)
        {
            string[] parsedArguments = null;
            if (!string.IsNullOrEmpty(arguments))
            {
                if (arguments.Contains("\""))
                {
                    Collection<string> args = new Collection<string>();
                    string arg = string.Empty;
                    bool quoteOpen = false;
                    
                    for (int i = 0; i < arguments.Length; i++)
                    {
                        Char c = arguments[i];

                        if (c == '"')
                        {
                            quoteOpen = !quoteOpen;
                        }

                        if (c!= '"' && (c!= ',' || quoteOpen))
                        {
                            arg += c;
                        }

                        if (!quoteOpen && (c == ',' || i == arguments.Length - 1))
                        {
                            args.Add(arg);
                            arg = string.Empty;
                        }
                    }
                    
                    parsedArguments = new string[args.Count];
                    args.CopyTo(parsedArguments, 0);
                }
                else
                {
                    parsedArguments = arguments.Split(',');
                }
            }

            return parsedArguments;
        }

        /// <summary>
        /// Parses a property type
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns><see cref="PropertyType"/></returns>
        public static PropertyType ParsePropertyType(string type)
		{
			if (string.IsNullOrEmpty(type))
			{
				throw new ArgumentNullException(type);
			}
			
			PropertyType returnValue = PropertyType.None;

		    if (type.Equals("text", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = PropertyType.Text;
		    }
		    else if (type.Equals("selecteditem", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = PropertyType.SelectedItem;
		    }
		    else if (type.Equals("checked", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = PropertyType.Checked;
		    }
		    else if (type.Equals("value", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = PropertyType.Value;
		    }
		    else if (type.Equals("filename", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = PropertyType.FileName;
		    }
            else if (type.StartsWith("attribute:", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = PropertyType.Attribute;
            }
		    else
		    {
		        throw new ArgumentOutOfRangeException(
		            string.Format(CultureInfo.InvariantCulture, "Can not parse unknown property type: {0}.", type));
		    }

		    return returnValue;
		}
		
		#endregion
		
		#region Private static methods
		
		/// <summary>
		/// Responsible for creating instances or derived types of <see cref="Command" />, based on
		/// the value of <paramref name="type">command type</paramref>.
		/// </summary>
		/// <param name="type">MethodType of command to create</param>
		/// <returns>Command for the given <paramref name="type">command type</paramref></returns>
		private static Command Create(CommandType type)
		{
			Command returnValue = null;
			switch(type)
			{
				case CommandType.Back:
                case CommandType.BringToFront:
				case CommandType.Forward:
                case CommandType.Flash:
				case CommandType.Click:
                case CommandType.ClickNoWait:
                case CommandType.Refresh:
				case CommandType.DoubleClick:
				case CommandType.GoTo:
                case CommandType.Close:
                case CommandType.AttachToIE:
                case CommandType.PressTab:
                case CommandType.Focus:
                case CommandType.ClearCache:
                case CommandType.ClearCookies:
                case CommandType.Screenshot:
                case CommandType.SetCookie:
                case CommandType.GetCookie:
                    returnValue = new Command();
					break;
                case CommandType.AttachDialogHandler:
                case CommandType.GetJavaDialogMessage:
			        returnValue = new DialogHandlerCommand();
			        break;
				case CommandType.Validate:
                case CommandType.ValidateItems:
                case CommandType.ValidateLastFound:
					returnValue = new ValidateCommand();
					break;
				case CommandType.SetValue:
				case CommandType.SetValues:
					returnValue = new SetValueCommand();
					break;
                case CommandType.AppendText:
                case CommandType.TypeText:
                case CommandType.ClearText:
                case CommandType.SelectText:
			        returnValue = new TextFieldCommand();
			        break;
                case CommandType.Settings:
			        returnValue = new SettingsCommand();
			        break;
				default:
					throw new ArgumentOutOfRangeException("command", string.Format(CultureInfo.InvariantCulture, "Can not create a command object for unknown command type: {0}", type));
			}
			
			return returnValue;
		}
		
		/// <summary>
		/// Validates if the required arguments for a given command have been specified
		/// </summary>
		/// <returns><c>true</c> if required arguments are present otherwise returns <c>false</c>.</returns>
		private static bool ValidateRequiredCommandArguments(Command command)
		{
			bool returnValue;
			
			switch(command.CommandType)
			{
				case CommandType.Back:
                case CommandType.BringToFront:
				case CommandType.Forward:
				case CommandType.Click:
                case CommandType.ClickNoWait:
				case CommandType.DoubleClick:
                case CommandType.AttachToIE:
                case CommandType.Close:
                case CommandType.Refresh:
                case CommandType.Flash:
                case CommandType.PressTab:
                case CommandType.Focus:
                case CommandType.ClearText:
                case CommandType.SelectText:
                case CommandType.ClearCache:
                case CommandType.ClearCookies:
					returnValue = true;
					break;
                case CommandType.AppendText:
                case CommandType.GoTo:
                case CommandType.TypeText:
                case CommandType.Screenshot:
                case CommandType.ValidateLastFound:
                	returnValue = command.Arguments.Count == 1;
					break;
				case CommandType.SetValue:
                case CommandType.SetCookie:
                case CommandType.GetCookie:
                case CommandType.Validate:
                case CommandType.AttachDialogHandler:
                case CommandType.GetJavaDialogMessage:
                    returnValue = command.Arguments.Count == 2;
					break;
				case CommandType.SetValues:
                case CommandType.ValidateItems:
					returnValue = command.Arguments.Count > 2;
					break;
                case CommandType.Settings:
			        returnValue = command.Arguments.Count == 2;
			        break;
				default:
					throw new ArgumentOutOfRangeException("command", string.Format(CultureInfo.InvariantCulture, "Can not validate unknown command type: {0}", command.CommandType));
			}
			
			return returnValue;
		}
		
		#endregion	    
	}
}
